﻿using System;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;

namespace SBToolkit.Core
{
    /// <summary>
    /// Provides cloning functionality.
    /// </summary>
    public static class Cloner
    {
        /// <summary>
        /// Creates deep clone version of the underlying object.
        /// </summary>
        /// <typeparam name="T">Type of the object to clone.</typeparam>
        /// <param name="obj">The object to clone.</param>
        /// <returns>Deep clone version of the underlying object.</returns>
        public static T DeepClone<T>(this T obj)
        {
            T cloned = default(T);
            
            using (var ms = new MemoryStream())
            {
                var serializer = new BinaryFormatter();
                serializer.Serialize(ms, obj);
                ms.Position = 0;
                cloned = (T)serializer.Deserialize(ms);
            }
            
            return cloned;
        }

        /// <summary>   
        /// Clones properties from an original object to a destination object.   
        /// </summary>   
        /// <typeparam name="T1">Type of the object to clone properties from.</typeparam>   
        /// <typeparam name="T2">Type of the object to clone properties to.</typeparam>   
        /// <param name="origin">Original object.</param>
        /// <param name="destination">Destination object.</param>
        public static void CloneProperties<T1, T2>(this T1 origin, T2 destination)
        {
            if (destination == null)
            {
                throw new ArgumentNullException("destination");
            }
            
            // loop through each property in the destination   
            foreach (var prop in destination.GetType().GetProperties())
            {
                // find and set val if we can find a matching property name and matching type in the origin with the origin's value   
                if (origin != null && prop.CanWrite)
                {
                    origin.GetType().GetProperties()
                        .Where(x => x.CanRead && x.Name == prop.Name && x.PropertyType == prop.PropertyType)
                        .ToList()
                        .ForEach(x => prop.SetValue(destination, x.GetValue(origin, null), null));
                }
            }
        }   
    }
}